/*
 * AIEngine a new generation network intrusion detection system.
 *
 * Copyright (C) 2013-2023  Luis Campo Giralte
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * Written by Luis Campo Giralte <luis.camp0.2009@gmail.com>
 *
 */
#include "PacketDispatcher.h"
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>

#define IFF_LOWER_UP    0x10000

namespace aiengine {

PacketDispatcher::PacketDispatcher(const std::string &source, int buffer_size):
	io_service_(),
        signals_(io_service_, SIGINT, SIGTERM),
	input_name_(source),
	pcap_buffer_size_(buffer_size) {

#if defined(IS_LINUX)
	nl_sock_ = SharedPointer<NetlinkSocket>(new NetlinkSocket(io_service_));

	nl_sock_->open(NetlinkProtocol(NETLINK_ROUTE));
	nl_sock_->bind(NetlinkEndpoint<NetlinkProtocol>());
#endif
        signals_.async_wait(boost::bind(&boost::asio::io_service::stop, &io_service_));
}

PacketDispatcher::~PacketDispatcher() {

	// The object can be destroy with a stack attached
	if (current_network_stack_)
		current_network_stack_->stopAsioService();

	io_service_.stop();
}

bool PacketDispatcher::is_ethernet_present() const {

	if (pcap_) {
		int ret = pcap_datalink(pcap_);
		if (ret == DLT_EN10MB)
			return true;
	}
	return false;
}

int PacketDispatcher::datalink_size() const {

	int datalink_size = 0;

	if (pcap_) {
		int ret = pcap_datalink(pcap_);
		if (ret == DLT_EN10MB)
			datalink_size = ETHER_HDR_LEN;
		else if (ret == DLT_NULL)
			datalink_size = 4;
		else if (ret == DLT_LINUX_SLL)
			datalink_size = 16;
	}
	return datalink_size;
}

void PacketDispatcher::info_message(const std::string &msg) {

	aiengine::information_message(msg);
}

void PacketDispatcher::error_message(const std::string &msg) {

	aiengine::error_message(msg);
}

void PacketDispatcher::statistics() {

        statistics(OutputManager::getInstance()->out());
}

void PacketDispatcher::set_stack(const SharedPointer<NetworkStack> &stack) {

        current_network_stack_ = stack;
	stack_name_ = stack->name();
        setDefaultMultiplexer(stack->getLinkLayerMultiplexer().lock());
        stack->setAsioService(io_service_, input_name_);
}

void PacketDispatcher::setStack(const SharedPointer<NetworkStack> &stack) {

	set_stack(stack);
}

void PacketDispatcher::setDefaultMultiplexer(MultiplexerPtr mux) {

	defMux_ = mux;
	auto proto = mux->getProtocol();
	eth_ = std::dynamic_pointer_cast<EthernetProtocol>(proto);
}

int PacketDispatcher::get_mtu_of_network_device(const std::string &name) {

	if (name.compare("any") == 0)
		return ETHER_MAX_LEN;

	struct ifreq ifr;
        int fd = socket(AF_INET, SOCK_DGRAM, 0);

	if (fd != -1) {
        	ifr.ifr_addr.sa_family = AF_INET;
        	strncpy(ifr.ifr_name , name.c_str() , IFNAMSIZ - 1);

		if (ioctl(fd, SIOCGIFMTU, &ifr) == 0) {
			// Use the global namespace for link with the system call close
			::close(fd);
			return ifr.ifr_mtu;
		}
		::close(fd);
	}

        std::ostringstream msg;
        msg << "Can not get MTU of device:" << input_name_.c_str();

	AIWARN << msg.str();

        error_message(msg.str());
	return 0;
}

void PacketDispatcher::open_device(const std::string &device, uint32_t buffer_size) {

	std::ostringstream msg;
	char errorbuf[PCAP_ERRBUF_SIZE];
#if defined(IS_FREEBSD) || defined(IS_DARWIN)
	int timeout = 1000; // miliseconds
#else
	int timeout = -1;
#endif

#if defined(IS_LINUX) && defined(HAVE_NETFILTER_QUEUE)
	if (device.rfind("netfilter", 0) == 0) {
		std::string value(device);

		value.erase(0, 9);
		open_netfilter_queue(std::stoi(value));
		return;
	}
#endif
	if (pcap_ = pcap_create(device.c_str(), errorbuf); pcap_ != nullptr) {
		int status;

		if ((status = pcap_set_timeout(pcap_, timeout)) != 0)
			msg << device << ": pcap_set_timeout failed:" << pcap_statustostr(status);

		if ((status = pcap_set_snaplen(pcap_, PACKET_RECVBUFSIZE)) != 0) {
			msg.clear();
			msg << device << ": Can't set snapshot length:" << pcap_statustostr(status);
		}

		if ((status = pcap_set_buffer_size(pcap_, buffer_size)) != 0) {
			msg.clear();
			msg << device << ": Can't set buffer size:" << pcap_statustostr(status);
		}

		if ((status = pcap_set_immediate_mode(pcap_, 1)) != 0) {
			msg.clear();
			msg << device << ": Can't set immediate mode:" << pcap_statustostr(status);
		}

		if ((status = pcap_activate(pcap_)) == 0) {

			int ifd = pcap_get_selectable_fd(pcap_);
			if (pcap_setnonblock(pcap_, 1, errorbuf) == 1) {
				msg.clear();
				msg << device << ": pcap_setnonblock failed:" << errorbuf;
			} else {
				stream_ = PcapStreamPtr(new PcapStream(io_service_));

				// just update the variable if all is good
				pcap_buffer_size_ = buffer_size;
				stream_->assign(::dup(ifd));
				device_is_ready_ = true;
				input_name_ = device;
				return;
			}
		} else {
			msg.clear();
			msg << device << ": " << pcap_statustostr(status) << ":" << pcap_geterr(pcap_);
		}
	} else {
		msg << "Device:" << device.c_str() << " error:" << errorbuf;
	}

	AIWARN << msg.str();

	error_message(msg.str());

	device_is_ready_ = false;
}

void PacketDispatcher::close_device(void) {

	if ((device_is_ready_)and(pcap_)) {
		stream_->close();
		pcap_close(pcap_);
		pcap_ = nullptr;
		device_is_ready_ = false;
	}
}

void PacketDispatcher::open_fifo_file(const std::string &fifoname) {

        char errorbuf[PCAP_ERRBUF_SIZE];

        if (pcap_ = pcap_open_offline(fifoname.c_str(), errorbuf); pcap_ == nullptr) {
                device_is_ready_ = false;
		pcap_is_fifo_ = false;
                std::ostringstream msg;
                msg << "Unkown fifo file:" << fifoname.c_str();

                AIWARN << msg.str();

                error_message(msg.str());
        } else {
                int ifd = pcap_get_selectable_fd(pcap_);
                if (pcap_setnonblock(pcap_, 1, errorbuf) == 1) {
			std::ostringstream msg;

			msg << fifoname << ": pcap_setnonblock failed:" << errorbuf;
			AIWARN << msg.str();
			pcap_is_fifo_ = false;
			device_is_ready_ = false;
		} else {
			input_name_ = fifoname;
			stream_ = PcapStreamPtr(new PcapStream(io_service_));
                        device_is_ready_ = true;
			pcap_is_fifo_ = true;
                        stream_->assign(::dup(ifd));
                }
	}
}

void PacketDispatcher::open_pcap_file(const std::string &filename) {

	char errorbuf[PCAP_ERRBUF_SIZE];

        if (pcap_ = pcap_open_offline(filename.c_str(), errorbuf); pcap_ == nullptr) {
		pcap_file_ready_ = false;
        	std::ostringstream msg;
		msg << "Unkown pcapfile:" << filename.c_str();

		AIWARN << msg.str();

		pcap_file_header_offset_ = 0;
		error_message(msg.str());
	} else {
		pcap_file_ready_ = true;
		input_name_ = filename;

		// Gets the offset of the pcapfile, probably is
		// after the pcapfile header
		pcap_file_header_offset_ = ftell(pcap_file(pcap_));
	}
}

void PacketDispatcher::close_pcap_file(void) {

	if (pcap_file_ready_) {
		pcap_close(pcap_);
		pcap_file_ready_ = false;
		pcap_ = nullptr;
	}
}

void PacketDispatcher::read_raw_network(boost::system::error_code ec) {

// This prevents a problem on the boost asio signal
// remove this if when boost will be bigger than 1.50
#ifdef PYTHON_BINDING
#if BOOST_VERSION >= 104800 && BOOST_VERSION < 105000
	if (PyErr_CheckSignals() == -1) {
		std::ostringstream msg;
		msg << "Throwing exception from python";

		error_message(msg.str());
		throw std::runtime_error("Python exception\n");
	}
#endif
#endif

	if ((!ec)and(pcap_next_ex(pcap_, &packet_header_, &packet_) >= 0)) {
		packet_length_ = packet_header_->len;
		forward_current_raw_packet();
	}

	if (!ec || ec == boost::asio::error::would_block)
		start_read_raw_network();
}

void PacketDispatcher::read_ethernet_network(boost::system::error_code ec) {

// This prevents a problem on the boost asio signal
// remove this if when boost will be bigger than 1.50
#ifdef PYTHON_BINDING
#if BOOST_VERSION >= 104800 && BOOST_VERSION < 105000
	if (PyErr_CheckSignals() == -1) {
		std::ostringstream msg;
		msg << "Throwing exception from python";

		error_message(msg.str());
		throw std::runtime_error("Python exception\n");
	}
#endif
#endif

	if ((!ec)and(pcap_next_ex(pcap_, &packet_header_, &packet_) >= 0)) {
		packet_length_ = packet_header_->len;
		forward_current_ethernet_packet();
	}

	if (!ec || ec == boost::asio::error::would_block)
		start_read_ethernet_network();
}


#if defined(IS_LINUX)

void PacketDispatcher::read_netlink(boost::system::error_code ec) {

	struct nlmsghdr *nlm = (struct nlmsghdr *)netlink_buffer_.data();

	if ((nlm)and(nlm->nlmsg_type == RTM_NEWLINK)) {
		char ifname[IF_NAMESIZE + 1] = {0};
		ifinfomsg *ifi = (ifinfomsg*)NLMSG_DATA(nlm);
		rtattr *rta = (rtattr *) ((char *)ifi + NLMSG_ALIGN(sizeof(*ifi)));
		int len = NLMSG_PAYLOAD(nlm, sizeof(*ifi));

		while (RTA_OK(rta, len)) {
			if (rta->rta_type == IFLA_IFNAME) {
				strncpy(ifname, (char*)RTA_DATA(rta), sizeof(ifname));
				break;
			}
			rta = RTA_NEXT(rta, len);
		}

		// We only check the status of the network device that is open
		if (input_name_.compare(ifname) == 0) {
			bool if_lower_up = (ifi->ifi_flags & IFF_LOWER_UP) ? true : false;
			bool is_running = (ifi->ifi_flags & IFF_RUNNING) ? true : false;

			if ((!device_is_ready_)and(if_lower_up)and(is_running)) {
				// The network device is up and running so we need to reopen the device
                                open_device(input_name_, pcap_buffer_size_);
#if defined(PYTHON_BINDING)
				if (network_callback_.haveCallback())
					network_callback_.executeCallback();
#endif
				// after open and run a callback we capture the packets
				run_device();
			} else if ((device_is_ready_)and(!if_lower_up)) {
				// the network device is down we need to close the descriptors
				std::ostringstream msg;
				msg << "Network device '" << input_name_ << "' is down";

				AIWARN << msg.str();

				error_message(msg.str());
				device_is_ready_ = false;
#if defined(PYTHON_BINDING)
				if (network_callback_.haveCallback())
					network_callback_.executeCallback();
#endif
			}
		}
	}
	start_read_netlink();
}

#if defined(HAVE_NETFILTER_QUEUE)

void PacketDispatcher::read_netfilter(boost::system::error_code ec) {

	int ret = recv(netfilter_fd_, netfilter_buffer_, NETFILTER_QUEUE_BUFFER, 0);
	if (ret > 0)
		nfq_handle_packet(nfq_h_, (char*)netfilter_buffer_, ret);

        if (!ec || ec == boost::asio::error::would_block)
		start_read_netfilter();
}

void PacketDispatcher::start_read_netfilter(void) {

	read_in_progress_ = false;
	if (!read_in_progress_) {
		read_in_progress_ = true;

		stream_->async_read_some(boost::asio::null_buffers(),
			boost::bind(&PacketDispatcher::read_netfilter, this,
                                boost::asio::placeholders::error));
	}
}

void PacketDispatcher::open_netfilter_queue(const int nqueue) {

	if (nfq_h_ = nfq_open()) {
		if ((nfq_unbind_pf(nfq_h_, AF_INET) >= 0)and(nfq_bind_pf(nfq_h_, AF_INET) >= 0)) {

			nfq_qh_ = nfq_create_queue(nfq_h_,  nqueue, &netfilter_callback, this);
			if ((nfq_qh_)and(nfq_set_mode(nfq_qh_, NFQNL_COPY_PACKET, 0xffff) >= 0)) {
				// The initialization of the netfilter layer is correct
                                stream_ = PcapStreamPtr(new PcapStream(io_service_));

				netfilter_fd_ = nfq_fd(nfq_h_);
                                stream_->assign(::dup(netfilter_fd_));
                                input_name_ = "netfilter" + std::to_string(nqueue);
				device_is_ready_ = true;
				netfilter_is_ready_ = true;
				return;
			}
		}
	}
        std::ostringstream msg;

	device_is_ready_ = false;
	netfilter_is_ready_ = false;

	msg << "Can not open queue " << nqueue << " from netfilter";
        AIWARN << msg.str();

        error_message(msg.str());
}

int PacketDispatcher::netfilter_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) {

	PacketDispatcher *pthis = static_cast<PacketDispatcher*>(data);
	struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfa);
	uint32_t id = ntohl(ph->packet_id);
	int veredict = NF_ACCEPT;
	uint8_t *pkt_data;

	int length = nfq_get_payload(nfa, &pkt_data);
	if (length > 0) {
		struct timeval timestamp {0, 0};

		int ret = nfq_get_timestamp(nfa, &timestamp);
		if (ret != 0) // dont like this solution
			gettimeofday(&timestamp, NULL);

		length += 14; // From ethernet header
		pthis->netfilter_buffer_[32 + 14 + 12] = 0x08;
		// WARNING std::memcpy(&packet_buffer[14], pkt_data, len);

		++ pthis->total_packets_;
		pthis->total_bytes_ += length;

		pthis->stats_.packet_time = timestamp.tv_sec;

		if (pthis->defMux_) {
			pthis->current_packet_.setPayload(&pthis->netfilter_buffer_[14 + 32]);
			pthis->current_packet_.setPayloadLength(length);
			pthis->current_packet_.setPrevHeaderSize(0);
			pthis->current_packet_.setPacketTime(timestamp.tv_sec);
			pthis->current_packet_.setEvidence(false);

			if (pthis->defMux_->accept(pthis->current_packet_)) {
				pthis->defMux_->setPacket(&pthis->current_packet_);
				pthis->defMux_->setNextProtocolIdentifier(pthis->eth_->getEthernetType());
				pthis->defMux_->forward(pthis->current_packet_);
				if ((pthis->have_evidences_)and(pthis->current_packet_.haveEvidence()))
					pthis->em_->write(pthis->current_packet_);
			}

			// Check if some component mark the packet for drop
			if (pthis->current_packet_.isAccept() == false) {
				veredict = NF_DROP;
				++ pthis->total_drop_packets_;
				pthis->total_drop_bytes_ += length;
			} else {
				++ pthis->total_accept_packets_;
				pthis->total_accept_bytes_ += length;
			}
		}
	}
	return nfq_set_verdict(qh, id, veredict, 0, NULL);
}

void PacketDispatcher::run_netfilter(void) {

	if ((device_is_ready_)and(netfilter_is_ready_)) {
		try {
			std::ostringstream msg;
			std::string nqueue(input_name_);

			nqueue.erase(0, 9);
			msg << "Processing packets from netfilter queue: " << nqueue;

			info_message(msg.str());
			AIINFO << msg.str();

			if ((current_network_stack_)&&(eth_)) {
				uint64_t memory = current_network_stack_->getAllocatedMemory();

				std::string unit = "Bytes";

				unitConverter(memory,unit);

				msg.clear();
				msg.str("");
				msg << "Stack '" << stack_name_ << "' using " << memory << " " << unit << " of memory";
				info_message(msg.str());
			} else {
				msg.clear();
				msg.str("");
				msg << "No stack configured";
				warning_message(msg.str());
			}

			AIINFO << msg.str();

			status_ = PacketDispatcherStatus::RUNNING;
			start_read_netfilter();
#if defined(PYTHON_BINDING) || defined(RUBY_BINDING) || defined(LUA_BINDING)
			start_read_user_input();
#endif
			io_running_ = true;
			io_service_.run();
		} catch (const std::exception& e) {
			std::ostringstream msg;
			msg << ":ERROR:" << e.what() << std::endl;
			msg << boost::diagnostic_information(e);

			AIERROR << msg.str();
			error_message(msg.str());
		}
		status_ = PacketDispatcherStatus::STOPPED;
	}
}

void PacketDispatcher::close_netfilter(void) {

	if (netfilter_is_ready_) {
		stream_->close();
		nfq_destroy_queue(nfq_qh_);
		nfq_unbind_pf(nfq_h_, AF_INET);
		nfq_close(nfq_h_);
		nfq_qh_ = nullptr;
		nfq_h_ = nullptr;
		netfilter_is_ready_ = false;
		device_is_ready_ = false;
	}
}

#endif // HAVE_NETFILTER_QUEUE support

#endif

void PacketDispatcher::forward_current_ethernet_packet() {

	++total_packets_;
	total_bytes_ += packet_length_;

	stats_.packet_time = packet_header_->ts.tv_sec;

	if (defMux_) {
		current_packet_.setPayload(packet_);
		current_packet_.setPayloadLength(packet_length_);
		current_packet_.setPrevHeaderSize(0);
		current_packet_.setPacketTime(packet_header_->ts.tv_sec);
		current_packet_.setEvidence(false);

		if (defMux_->accept(current_packet_)) {
			defMux_->setPacket(&current_packet_);
			defMux_->setNextProtocolIdentifier(eth_->getEthernetType());
			defMux_->forward(current_packet_);

			if ((have_evidences_)and(current_packet_.haveEvidence()))
				em_->write(current_packet_);
                }
	}
}

void PacketDispatcher::forward_current_raw_packet() {

	++total_packets_;
	total_bytes_ += packet_length_;

	stats_.packet_time = packet_header_->ts.tv_sec;

	if (defMux_) {
		current_packet_.setPayload(packet_);
		current_packet_.setPayloadLength(packet_length_);
		current_packet_.setPrevHeaderSize(0);
		current_packet_.setPacketTime(packet_header_->ts.tv_sec);
		current_packet_.setEvidence(false);

		if (defMux_->accept(current_packet_)) {
			defMux_->setPacket(&current_packet_);
			defMux_->setNextProtocolIdentifier(current_network_stack_->getLinkLayerType());
			defMux_->forward(current_packet_);

			if ((have_evidences_)and(current_packet_.haveEvidence()))
				em_->write(current_packet_);
                }
	}
}

void PacketDispatcher::start_read_raw_network(void) {

	read_in_progress_ = false;
	if (!read_in_progress_) {
		read_in_progress_ = true;

		stream_->async_read_some(boost::asio::null_buffers(),
			boost::bind(&PacketDispatcher::read_raw_network, this,
                                boost::asio::placeholders::error));
	}
}

void PacketDispatcher::start_read_ethernet_network(void) {

	read_in_progress_ = false;
	if (!read_in_progress_) {
		read_in_progress_ = true;

		stream_->async_read_some(boost::asio::null_buffers(),
                	boost::bind(&PacketDispatcher::read_ethernet_network, this,
                                boost::asio::placeholders::error));
	}
}

#if defined(PYTHON_BINDING) || defined(RUBY_BINDING) || defined(LUA_BINDING)
void PacketDispatcher::start_read_user_input(void) {

	user_shell_->readUserInput();
}

#endif

#if defined(IS_LINUX)

void PacketDispatcher::start_read_netlink(void) {

	nl_sock_->async_receive(boost::asio::buffer(netlink_buffer_, NETLINK_BUFFER),
		boost::bind(&PacketDispatcher::read_netlink, this,
			boost::asio::placeholders::error));
}

#endif

#if defined(STAND_ALONE_TEST) || defined(TESTING)
void PacketDispatcher::setMaxPackets(int packets) {

	max_packets_ = packets;
}

void PacketDispatcher::setFromPackets(int packets) {

	from_packets_ = packets;
}
#endif

void PacketDispatcher::run_pcap(void) {

	int ret;
        std::ostringstream msg;
        msg << "Processing packets from pcap file " << input_name_.c_str();
       	info_message(msg.str());

	AIINFO << msg.str();

	if (eth_)
		eth_->setMaxEthernetLength(ETHER_MAX_LEN * 4); // Increase the size to a big value probably 65243 is the best

	if (current_network_stack_) {
		uint64_t memory = current_network_stack_->getAllocatedMemory();
		std::string unit = "Bytes";

		unitConverter(memory,unit);

		msg.clear();
		msg.str("");
        	msg << "Stack '" << stack_name_ << "' using " << memory << " " << unit << " of memory";
       		info_message(msg.str());
        } else {
                msg.clear();
                msg.str("");
                msg << "No stack configured";
                warning_message(msg.str());
        }

#if defined(STAND_ALONE_TEST) || defined(TESTING)
	int total_exclude_packets = 0;
#endif
	status_ = PacketDispatcherStatus::RUNNING;

	if (defMux_)
		defMux_->setHeaderSize(datalink_size());

#if defined(PYTHON_BINDING) && defined(HAVE_PCAP_TIMERS)
	int32_t value = 0;
	int32_t second = 0;
#endif
	if (is_ethernet_present()) {
		while ((ret = pcap_next_ex(pcap_, &packet_header_, &packet_)) > 0) {
			// Friendly remminder:
			//     header_->len contains length this packet (off wire)
			//     header_->caplen length of portion present

#if defined(STAND_ALONE_TEST) || defined(TESTING)
			if (total_exclude_packets < from_packets_) {
				++total_exclude_packets;
				continue;
			}
#endif
			packet_length_ = packet_header_->caplen;

			forward_current_ethernet_packet();

#if defined(PYTHON_BINDING) && defined(HAVE_PCAP_TIMERS)
                        if (value != packet_header_->ts.tv_sec) {
                                if (value > 0) {
                                        for (int sec = value; sec < packet_header_->ts.tv_sec; ++sec) {
                                                if (second > 0)
                                                        tm_->runCallback(second);
                                                ++second;
                                        }
                                }
                                value = packet_header_->ts.tv_sec;
                        }
#endif

#if defined(STAND_ALONE_TEST) || defined(TESTING)
     			if (total_packets_ >= max_packets_)
				break;
#endif
		}
	} else {
		while ((ret = pcap_next_ex(pcap_, &packet_header_, &packet_)) > 0) {
			// Friendly remminder:
			//     header_->len contains length this packet (off wire)
			//     header_->caplen length of portion present

#if defined(STAND_ALONE_TEST) || defined(TESTING)
			if (total_exclude_packets < from_packets_) {
				++total_exclude_packets;
				continue;
			}
#endif
			packet_length_ = packet_header_->caplen;

			forward_current_raw_packet();

#if defined(PYTHON_BINDING) && defined(HAVE_PCAP_TIMERS)
                        if (value != packet_header_->ts.tv_sec) {
				if (value > 0) {
					for (int sec = value; sec < packet_header_->ts.tv_sec; ++sec) {
                                		if (second > 0)
                                        		tm_->runCallback(second);
                                		++second;
					}
				}
                                value = packet_header_->ts.tv_sec;
                        }
#endif

#if defined(STAND_ALONE_TEST) || defined(TESTING)
     			if (total_packets_ >= max_packets_)
				break;
#endif
		}
	}

	if (ret <= 0) { // There is no more packets on the pcap_
		// Rewind the pcap file to get the first packet
		if (fseek(pcap_file(pcap_), pcap_file_header_offset_, SEEK_SET) == 0) {
			// Just read again the first packet for get the time of the first packet
			if (pcap_next_ex(pcap_, &packet_header_, &packet_) >= 0) {
				stats_.first_packet_time = packet_header_->ts.tv_sec;

				pcap_files_duration_ += stats_.packet_time - stats_.first_packet_time;
			}
		}
	}
	status_ = PacketDispatcherStatus::STOPPED;
}

void PacketDispatcher::run_device(void) {

	if (device_is_ready_) {
        	std::ostringstream msg;

		msg << "Processing packets from ";
		if (pcap_is_fifo_)
			msg << "fifo ";
		else
			msg << "device ";
		msg << input_name_.c_str();

        	info_message(msg.str());
		AIINFO << msg.str();

        	if ((current_network_stack_)&&(eth_)) {
			uint64_t memory = current_network_stack_->getAllocatedMemory();

			if (pcap_is_fifo_)
				eth_->setMaxEthernetLength(ETHER_MAX_LEN);
			else
				eth_->setMaxEthernetLength(get_mtu_of_network_device(input_name_));

                	std::string unit = "Bytes";

                	unitConverter(memory,unit);

                	msg.clear();
                	msg.str("");
                	msg << "Stack '" << stack_name_ << "' using " << memory << " " << unit << " of memory";
                	info_message(msg.str());
#if defined(IS_LINUX)
			// We tell, at socket level, the pcap descriptor to just
			// to capture IPv4 or IPv6 packets depending on the link layer type
			// of the stack
			pcap_set_protocol_linux(pcap_, current_network_stack_->getLinkLayerType());
#endif
        	} else {
                	msg.clear();
                	msg.str("");
                	msg << "No stack configured";
                	warning_message(msg.str());
		}

		AIINFO << msg.str();

		try {
			status_ = PacketDispatcherStatus::RUNNING;
			if (defMux_)
				defMux_->setHeaderSize(datalink_size());
			if (is_ethernet_present()) {
				start_read_ethernet_network();
			} else {
				start_read_raw_network();
			}
#if defined(PYTHON_BINDING) || defined(RUBY_BINDING) || defined(LUA_BINDING)
			start_read_user_input();
#endif
#if defined(IS_LINUX)
			start_read_netlink();
#endif
			io_running_ = true;
			io_service_.run();
		} catch (const std::exception& e) {
        		std::ostringstream msg;
        		msg << ":ERROR:" << e.what() << std::endl;
			msg << boost::diagnostic_information(e);

			AIERROR << msg.str();
			error_message(msg.str());
        	}
		status_ = PacketDispatcherStatus::STOPPED;
	} else {

                std::ostringstream msg;
                msg << "The device is not ready to run";

		AIINFO << msg.str();
                info_message(msg.str());
	}
}

void PacketDispatcher::open(const std::string &source) {

	open(source, pcap_buffer_size_);
}

void PacketDispatcher::open(const std::string &source, int buffer_size) {


#if defined(IS_FREEBSD) || defined(IS_DARWIN)
	boost::filesystem::path path(source);
#else
	std::filesystem::path path(source);
#endif

#if defined(IS_FREEBSD) || defined(IS_DARWIN)
	if (boost::filesystem::status(path).type() == boost::filesystem::file_type::fifo_file) {
#else
	if (std::filesystem::is_fifo(path)) {
#endif
		// We can pipe the output of tcpdump into a fifo file and
		// redirect to the engine in the same way as a network device
		open_fifo_file(source);
	} else {
#if defined(IS_FREEBSD) || defined(IS_DARWIN)
		if (boost::filesystem::is_regular_file(path)) {
#else
		if (std::filesystem::is_regular_file(path)) {
#endif
			open_pcap_file(source);
		} else {
			bool valid_device = false;
			if ((source.compare("any") != 0)and(source.rfind("netfilter", 0) != 0)) {
				pcap_if_t *alldevs = nullptr;
				pcap_if_t *d = nullptr;
				char errbuf[PCAP_ERRBUF_SIZE];

				/* Retrieve the device list from the local machine */
				if (pcap_findalldevs(&alldevs, errbuf) == -1) {
					std::ostringstream msg;
					msg << "Can not get list of network devices";

					AIWARN << msg.str();

					error_message(msg.str());
					exit(1);
				}

				for(d = alldevs; d != nullptr; d = d->next) {
					if (source.compare(d->name) == 0) {
						valid_device = true;
						break;
					}
				}
				pcap_freealldevs(alldevs);
			} else
				valid_device = true;

			if (valid_device) {
				close_device();
				open_device(source, buffer_size);
			} else {
				std::ostringstream msg;
				msg << "Unknown device or file input:" << source.c_str();

				AIWARN << msg.str();

				warning_message(msg.str());
			}
		}
	}
}

void PacketDispatcher::run(void) {

#if defined(IS_LINUX) && defined(HAVE_NETFILTER_QUEUE)
	if (netfilter_is_ready_) {
		run_netfilter();
		return;
	}
#endif
	if (device_is_ready_) {
		run_device();
	} else {
		if (pcap_file_ready_)
			run_pcap();

                if ((http_acceptor_.is_open())and(io_running_ == false)) {
                        try {
                                status_ = PacketDispatcherStatus::RUNNING;
                                io_running_ = true;
                                io_service_.run();
                        } catch (const std::exception& e) {
                                std::ostringstream msg;
                                msg << ":ERROR:" << e.what() << std::endl;
                                msg << boost::diagnostic_information(e);

				AIERROR << msg.str();
                                error_message(msg.str());
                        }
                        status_ = PacketDispatcherStatus::STOPPED;
                }
	}
}

void PacketDispatcher::close(void) {

#if defined(IS_LINUX) && defined(HAVE_NETFILTER_QUEUE)
	if (netfilter_is_ready_) {
		close_netfilter();
		return;
	}
#endif
        if (device_is_ready_) {
                close_device();
        } else {
                if (pcap_file_ready_) {
                        close_pcap_file();
                }
        }
}

void PacketDispatcher::setPcapFilter(const char *filter) {

	if (((device_is_ready_)or(pcap_file_ready_))and(pcap_)) {
		struct bpf_program fp;
		char *c_filter = const_cast<char*>(filter);

		if (pcap_compile(pcap_, &fp, c_filter, 1, PCAP_NETMASK_UNKNOWN) == 0) {
			if (pcap_setfilter(pcap_, &fp) == 0) {
				std::ostringstream msg;
				pcap_filter_ = filter;
                		msg << "Pcap filter set:" << filter;

                		info_message(msg.str());
			}
			pcap_freecode(&fp);
		} else {
			std::ostringstream msg;
			msg << "Wrong pcap filter";

			error_message(msg.str());
		}
	}
}

void PacketDispatcher::setEvidences(bool value) {

        if ((!have_evidences_)and(value)) {
                have_evidences_ = true;
                em_->enable();
        } else if ((have_evidences_)and(!value)) {
                have_evidences_ = false;
                em_->disable();
        }
}

bool PacketDispatcher::bind_to(const std::string &ip, const std::string &device) const {

	struct ifreq ifr;
	int fd = socket(AF_INET, SOCK_DGRAM, 0);

	if (fd != -1) {
		ifr.ifr_addr.sa_family = AF_INET;

		strncpy(ifr.ifr_name, device.c_str(), IFNAMSIZ-1);

		ioctl(fd, SIOCGIFADDR, &ifr);

		::close(fd);

		if (ip.compare(inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr)) == 0)
			return true;
	}
	return false;
}

#if defined(PYTHON_BINDING) || defined(RUBY_BINDING)

void PacketDispatcher::setShell(bool enable) {

        user_shell_->setShell(enable);
}

bool PacketDispatcher::getShell() const {

        return user_shell_->getShell();
}

void PacketDispatcher::setLogUserCommands(bool enable) {

	user_shell_->setLogUserCommands(enable);
}

bool PacketDispatcher::getLogUserCommands() const {

	return user_shell_->getLogUserCommands();
}

#endif

#if defined(LUA_BINDING)

void PacketDispatcher::setShell(lua_State *L, bool enable) {

        user_shell_->setShell(enable);
	user_shell_->setLuaInterpreter(L);
}

bool PacketDispatcher::getShell() const {

        return user_shell_->getShell();
}

#endif

#if defined(PYTHON_BINDING)

void PacketDispatcher::setAlarmCallback(PyObject *callback) {

	alarm_callback_.setCallback(callback);
}

void PacketDispatcher::setNetworkDeviceStatusCallback(PyObject *callback) {

	network_callback_.setCallbackWithNoArgs(callback);
}

void PacketDispatcher::addTimer(PyObject *callback, int seconds) {

	tm_->addTimer(callback, seconds);
}

void PacketDispatcher::removeTimer(int seconds) {

	tm_->removeTimer(seconds);
}

#elif defined(RUBY_BINDING)
void PacketDispatcher::addTimer(VALUE callback, int seconds) {

	tm_->addTimer(callback, seconds);
}
#elif defined(LUA_BINDING)
void PacketDispatcher::addTimer(lua_State* L, const char *callback, int seconds) {

	tm_->addTimer(L, callback, seconds);
}
#endif

#if defined(PYTHON_BINDING)


void PacketDispatcher::setPcapBufferSize(int32_t value) {

	if ((device_is_ready_)and(pcap_buffer_size_ != value)) {
		std::ostringstream msg;

		msg << "Please execute the method 'run' of the PacketDispatcher instance for commit the change";
		info_message(msg.str());

		AIINFO << msg.str();

		pcap_buffer_size_ = value;
	}
}


const char *PacketDispatcher::getHTTPIPAddress() const {

	return http_ip_address_.to_string().c_str();
}


// Sets the IP address of where the HTTP server is going
// to be listen to user requests
void PacketDispatcher::setHTTPIPAddress(const std::string &ip) {

	boost::system::error_code ec;

	boost::asio::ip::tcp::endpoint endpoint = http_acceptor_.local_endpoint(ec);

	if (ec) { // The port is not set all ready
		boost::asio::ip::address address = boost::asio::ip::make_address(ip, ec);
		if (!ec)
			http_ip_address_ = address;
		return;
	}

	// The port has been set by the user
	int port = endpoint.port();

	if (port > 0) {
		boost::asio::ip::address address = boost::asio::ip::make_address(ip, ec);
		if (!ec) {
			http_ip_address_ = address;
			bind_to_http(port);
		}
	} else { // The port is not set but we can set the address
		http_acceptor_.close();
	}
}

void PacketDispatcher::showSystemProcessInformation() const {

	System system;

	system.statistics(OutputManager::getInstance()->out());
}

void PacketDispatcher::setStack(const boost::python::object &stack) {

	if (stack.is_none()) {
		// The user sends a Py_None
		pystack_ = boost::python::object();
		stack_name_ = "None";
        	defMux_.reset();
		eth_ = nullptr;
		current_network_stack_ = nullptr;
	} else {
		boost::python::extract<SharedPointer<NetworkStack>> extractor(stack);

        	if (extractor.check()) {
        		SharedPointer<NetworkStack> pstack = extractor();
                	pystack_ = stack;

			// The NetworkStack have been extracted and now call the setStack method
                	set_stack(pstack);
        	} else {
			std::ostringstream msg;

			msg << "Can not extract NetworkStack from python object";
			error_message(msg.str());
		}
	}
}

PacketDispatcher& PacketDispatcher::__enter__() {

	open(input_name_);
        return *this;
}

bool PacketDispatcher::__exit__(boost::python::object type, boost::python::object val, boost::python::object traceback) {

	close();
        return type.ptr() == Py_None;
}

void PacketDispatcher::forwardPacket(const std::string &packet, int length) {

	packet_ = reinterpret_cast<const uint8_t *>(packet.c_str());
	packet_length_ = length;
	forward_current_ethernet_packet();
	return;
}

const char *PacketDispatcher::getStatus() const {

        if (status_ == PacketDispatcherStatus::RUNNING)
                return "running";
        else
                return "stoped";
}

void PacketDispatcher::addAuthorizedIPAddresses(const boost::python::list &items) {

        for (int i = 0; i < len(items); ++i) {
                // Check if is a std::string
                boost::python::extract<std::string> extractor(items[i]);
                if (extractor.check()) {
                        auto addr = extractor();

                        addAuthorizedIPAddress(addr);
                }
        }
}

boost::python::list PacketDispatcher::getAuthorizedIPAddresses() const {

        return allow_address_->getIPAddresses();
}

void PacketDispatcher::setAuthorizationCodeToken(const std::string &value) {

	if (value.length() > 7)
		authorization_code_token_ = value;
	else if (value.length() == 0)
		authorization_code_token_.clear();
}

// Users and APIs only should see the first 4 bytes
const char *PacketDispatcher::getAuthorizationCodeToken() const {
	static char auth_code[8] = {0};

	if (authorization_code_token_.length() > 0) {

		strncpy(auth_code, authorization_code_token_.c_str(), 4);
		auth_code[4] = auth_code[5] = auth_code[6] = '.';

	}
	return auth_code;
}

const char *PacketDispatcher::getRawAuthorizationCodeToken() const {

	return authorization_code_token_.c_str();
}

#endif

std::ostream& operator<< (std::ostream &out, const PacketDispatcher &pd) {

	pd.statistics(out);
        return out;
}

void PacketDispatcher::statistics(std::basic_ostream<char> &out) const {

	struct pcap_stat stats;
	std::ostringstream device_status;

	out << std::setfill(' ');
	out << "PacketDispatcher statistics\n";
	out << "\t" << "Connected to " << stack_name_;
	if (current_network_stack_)
		out << " (" << current_network_stack_->getMode() << ")";
	out << "\n";

	if ((device_is_ready_)or(pcap_file_ready_))
		out << "\tCapturing from:         " << std::setw(10) << input_name_.c_str() << "\n";

	if (device_is_ready_) {
#if defined(IS_DARWIN)
		DeviceStatus status = network_device_status();

		if (status == DeviceStatus::RUNNING) {
			device_status << "RUNNING";
		} else if (status == DeviceStatus::DOWN) {
			if (isatty(fileno(stdout))) {
				Color::Modifier red(Color::FG_RED);
				Color::Modifier def(Color::FG_DEFAULT);
				device_status << red << "     DOWN" << def;
			} else {
				device_status << "DOWN";
			}
		} else {
			if (isatty(fileno(stdout))) {
				Color::Modifier orange(Color::FG_ORANGE);
				Color::Modifier def(Color::FG_DEFAULT);
				device_status << orange << "  UNKNOWN" << def;
			} else {
				device_status << "UNKNOWN";
			}
		}
#else
		device_status << "RUNNING";
#endif
		std::string unit = "Bytes";
		uint64_t buffer_size = pcap_buffer_size_;

		out << "\tDevice status:           " << std::setw(9) << device_status.str() << "\n";

		if (pcap_) {
			unitConverter(buffer_size, unit);

			out << "\t" << "Pcap buffer size:      " << std::setw(10 - (int)unit.length()) << buffer_size << " " << unit << "\n";
		}
	}

        if ((pcap_)and(pcap_filter_.length() > 0))
                out << "\t" << "Pcap filter:" << pcap_filter_ << "\n";

	if ((device_is_ready_)and(pcap_)and(pcap_stats(pcap_, &stats) == 0)) {
                out << "\t" << "Total received packets: " << std::dec << std::setw(10) << stats.ps_recv << "\n"
			<< "\t" << "Total dropped packets:  " << std::dec << std::setw(10) << stats.ps_drop << "\n"
			<< "\t" << "Total ifdropped packets:" << std::dec << std::setw(10) << stats.ps_ifdrop << "\n";
        }

        out << "\t" << "Total process packets: " << std::dec << std::setw(11) << total_packets_ << "\n";
        out << "\t" << "Total bytes:  " << std::dec << std::setw(20) << total_bytes_ << "\n";

#if defined(IS_LINUX) && defined(HAVE_NETFILTER_QUEUE)
	if (netfilter_is_ready_) {
		out << "\t" << "Total accept packets: " << std::dec << std::setw(12) << total_accept_packets_ << "\n";
		out << "\t" << "Total drop packets:   " << std::dec << std::setw(12) << total_drop_packets_ << "\n";
		out << "\t" << "Total accept bytes:" << std::dec << std::setw(15) << total_accept_bytes_ << "\n";
		out << "\t" << "Total drop bytes:  " << std::dec << std::setw(15) << total_drop_bytes_ << "\n";
	}
#endif

	if (pcap_files_duration_ > 0) {
		int minutes = pcap_files_duration_ / 60;
		int hours = minutes / 60;;
		std::ostringstream tout;

		tout << std::setw(2) << std::setfill('0') << hours << ":"
			<< std::setw(2) << std::setfill('0') << minutes % 60 << ":"
			<< std::setw(2) << std::setfill('0') << pcap_files_duration_ % 60;
		out << "\t" << "Total pcap duration:    " << std::setw(10) << tout.str() << "\n";
	}

#if defined(PYTHON_BINDING) || defined(RUBY_BINDING) || defined(LUA_BINDING)

	std::ostringstream shell_status;

        if (device_is_ready_) {
                // Compute the number of packets per second and bytes.
                uint64_t packets_per_second = 0;
                uint64_t bytes_per_second = 0;

                compute_packets_bytes_rate(packets_per_second, bytes_per_second);

                out << "\t" << "Total packets/sec:      " << std::dec << std::setw(10) << packets_per_second << "\n"
			<< "\t" << "Total bytes/sec:    " << std::dec << std::setw(14) << bytes_per_second << "\n";
        }

	if (user_shell_->getShell())
		shell_status << "enabled on " << ttyname(0);
	else
		shell_status << "disabled";

	out << "\tShell:" << std::setw(28) <<  shell_status.str() << "\n";

        tm_->statistics(out);

        out << "\t" << "Total TCP server connections:" << std::dec << std::setw(5) << total_tcp_server_connections_ << "\n";

	if (http_acceptor_.is_open()) {
		int port = http_acceptor_.local_endpoint().port();
		std::ostringstream http_out;

		http_out << http_ip_address_.to_string() << ":" << port;
		out << "\t" << "HTTP server on:" << std::setw(19) << http_out.str() << "\n";
	}
#endif


#if defined(PYTHON_BINDING)
	if (authorization_code_token_.length() > 0)
		out << "\t" << "Authorization code:     " << std::setw(10) << getAuthorizationCodeToken() << "\n";
	if (network_callback_.haveCallback())
		out << "\t" << "Device callback:" << network_callback_.name() << "\n";
	if (alarm_callback_.haveCallback())
		out << "\t" << "Alarm callback: " << alarm_callback_.name() << "\n";
#endif
        if (have_evidences_) {
		out << std::endl;
                em_->statistics(out);
        }
	out.flush();
}

void PacketDispatcher::statistics(Json &out) const {

	out["stack"] = stack_name_;
        if (device_is_ready_) {
		struct pcap_stat stats;

                out["device"] = input_name_.c_str();
#if defined(IS_DARWIN)
		out["device_status"] = network_device_status();
#else
		out["device_status"] = DeviceStatus::RUNNING;
#endif
		// Compute the number of packets per second and bytes.
		uint64_t packets_per_second = 0;
		uint64_t bytes_per_second = 0;

		compute_packets_bytes_rate(packets_per_second, bytes_per_second);

		if ((pcap_)and(pcap_stats(pcap_, &stats) == 0)) {
			out["received packets"] = stats.ps_recv;
			out["dropped packets"] = stats.ps_drop;
			out["ifdropped packets"] = stats.ps_ifdrop;
		}

#if defined(IS_LINUX) && defined(HAVE_NETFILTER_QUEUE)
		if (netfilter_is_ready_) {
			out["accept_packets"] = total_accept_packets_;
			out["drop_packets"] = total_drop_packets_;
			out["accept_bytes"] = total_accept_bytes_;
			out["drop_bytes"] = total_drop_bytes_;
		}
#endif
		if (pcap_files_duration_ > 0) {
			out["buffer size"] = pcap_buffer_size_;
			out["packets/sec"] = packets_per_second;
			out["bytes/sec"] = bytes_per_second;
		}
	} else if (pcap_file_ready_) {
		out["pcapfile"] = input_name_.c_str();
	}

	if (pcap_filter_.length() > 0)
		out["pcap_filter"] = pcap_filter_;

	out["process packets"] = total_packets_;
	out["bytes"] = total_bytes_;

	if (have_evidences_) {
		Json j;

		em_->statistics(j);

		out["evidences"] = j;
	}

#if defined(PYTHON_BINDING) || defined(RUBY_BINDING) || defined(LUA_BINDING)
	out["shell"] = user_shell_->getShell();
#endif

}

void PacketDispatcher::status(void) {

	std::ostringstream msg;
        msg << "PacketDispatcher ";
	if (status_ == PacketDispatcherStatus::RUNNING)
		msg << "running";
	else
		msg << "stoped";
	msg << ", plug to " << stack_name_;
	msg << ", packets " << total_packets_ << ", bytes " << total_bytes_;

        info_message(msg.str());
}

void PacketDispatcher::showCurrentPayloadPacket(std::basic_ostream<char> &out) const {

	if ((device_is_ready_)or(pcap_file_ready_)) {
		if (current_network_stack_) {
			std::tuple<Flow*,Flow*> flows = current_network_stack_->getCurrentFlows();
			Flow *low_flow = std::get<0>(flows);
			Flow *high_flow = std::get<1>(flows);

			if (low_flow)
				out << "\tFlow: " << *low_flow << "\n" << std::endl;

			if (high_flow)
				out << "\tFlow: " << *high_flow << "\n" << std::endl;
		}
		if ((packet_)and(packet_header_))
			showPayload(out, packet_, packet_length_);

		out << std::dec;
	}
}

void PacketDispatcher::showCurrentPayloadPacket(Json &out) const {

        if ((device_is_ready_)or(pcap_file_ready_)) {
                if (current_network_stack_) {
                        std::tuple<Flow*,Flow*> flows = current_network_stack_->getCurrentFlows();
                        Flow *low_flow = std::get<0>(flows);
                        Flow *high_flow = std::get<1>(flows);

                        if (low_flow) {
				std::ostringstream flow_out;
				flow_out << *low_flow;

				if (high_flow)
					out["low_flow"] = flow_out.str();
				else
					out["flow"] = flow_out.str();
			}

                        if (high_flow) {
				std::ostringstream flow_out;
				flow_out << *high_flow;
                                out["high_flow"] = flow_out.str();
			}
                }
                if (packet_) {
                        std::vector<uint8_t> pkt;

                        for (int i = 0; i < packet_length_; ++i)
                                pkt.push_back(packet_[i]);

                        out["packet"] = pkt;
		}

        }
}

void PacketDispatcher::showCurrentPayloadPacket() const { 

	showCurrentPayloadPacket(OutputManager::getInstance()->out());
}

#if !defined(PYTHON_BINDING)

void PacketDispatcher::setStack(StackLan &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

void PacketDispatcher::setStack(StackMobile &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

void PacketDispatcher::setStack(StackLanIPv6 &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

void PacketDispatcher::setStack(StackVirtual &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

void PacketDispatcher::setStack(StackOpenFlow &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

void PacketDispatcher::setStack(StackMobileIPv6 &stack) {

	SharedPointer<NetworkStack> s(&stack);
	set_stack(s);
}

#endif

void PacketDispatcher::bind_to_http(int port) {

	http_acceptor_.close();
	std::ostringstream msg;
	boost::asio::ip::tcp::endpoint admin_endpoint(http_ip_address_, port);

	// Check that the IP is not bind to the listen device of the traffic
	if (bind_to(http_ip_address_.to_string(), input_name_)) {
		std::ostringstream msg;
		msg << "Management IP '" << http_ip_address_.to_string() << "' belongs to network device '" << input_name_ << "'";

		AIWARN << msg.str();
	}
	http_acceptor_.open(admin_endpoint.protocol());
	http_acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));

	try {
		http_acceptor_.bind(admin_endpoint);
		http_acceptor_.listen();
	} catch (const std::exception& e) {
		std::ostringstream msg;
		msg << "WARNING:" << e.what();

		AIWARN << msg.str();
		warning_message(msg.str());
	}
	accept_http_connections();
}

void PacketDispatcher::setHTTPPort(int port) {

	// If the port is bigger than zero this means that the user
	// wants to listen on some port
        if (port > 0) {
		bind_to_http(port);
        } else {
                http_acceptor_.close();
        }
}

int PacketDispatcher::getHTTPPort() const {

        return http_acceptor_.local_endpoint().port();
}

void PacketDispatcher::addAuthorizedIPAddress(const std::string &ip) {

	if (allow_address_->lookupIPAddress(ip) == false)
		allow_address_->addIPAddress(ip);
}

void PacketDispatcher::accept_http_connections() {

        // Acceptor for http connections from administration pourposes
        http_acceptor_.async_accept(http_socket_, [&] (boost::beast::error_code ec) {
                if (!ec) {
                        [[maybe_unused]] std::time_t now = std::time(nullptr);
                        std::string ipaddress(http_socket_.remote_endpoint().address().to_string());

                        // Check if the ip address is allow to connect
			if (allow_address_->lookupIPAddress(ipaddress)) {

				if (current_network_stack_) {
                        		// Allocate space for the new administration connection
                                	auto conn = std::make_shared<HTTPSession>(std::move(http_socket_), current_network_stack_, this);

					++ total_tcp_server_connections_;
                                	// Start processing the HTTP
                                	conn->start();
				} else {
					AIWARN << "No stack plugged to the PacketDispatcher";
					http_socket_.close();
				}
                        } else {
				AIWARN << "IP addresss " << ipaddress << " not allowed";
                                http_socket_.close();
			}
                }
                accept_http_connections();
        });
}

void PacketDispatcher::compute_packets_bytes_rate(uint64_t &packets_per_second, uint64_t &bytes_per_second) const {

	int seconds = difftime(stats_.packet_time, stats_.last_packet_time);
        packets_per_second = total_packets_ - stats_.last_total_packets_sample;
        bytes_per_second = total_bytes_ - stats_.last_total_bytes_sample;

        if (seconds > 0 ) {
        	packets_per_second = packets_per_second / seconds;
                bytes_per_second = bytes_per_second / seconds;
	}

        stats_.last_packet_time = stats_.packet_time; // update the last time we make the compute
        stats_.last_total_packets_sample = total_packets_;
        stats_.last_total_bytes_sample = total_bytes_;
}

uint64_t PacketDispatcher::getTotalReceivedPackets(void) const {

	struct pcap_stat stats;
	uint64_t value = 0;

	if ((device_is_ready_)and(pcap_stats(pcap_, &stats) == 0))
                value = stats.ps_recv;

	return value;
}
 
uint64_t PacketDispatcher::getTotalDroppedPackets(void) const {

	struct pcap_stat stats;
	uint64_t value = 0;

	if ((device_is_ready_)and(pcap_stats(pcap_, &stats) == 0))
                value = stats.ps_drop;

	return value;
}

uint64_t PacketDispatcher::getTotalIfDroppedPackets(void) const {

	struct pcap_stat stats;
	uint64_t value = 0;

	if ((device_is_ready_)and(pcap_stats(pcap_, &stats) == 0))
                value = stats.ps_ifdrop;

	return value;
}

#if defined(IS_DARWIN)

PacketDispatcher::DeviceStatus PacketDispatcher::network_device_status() const {

	PacketDispatcher::DeviceStatus status = DeviceStatus::UNKNOWN;
        struct ifreq ifr;

	int fd = socket(AF_INET, SOCK_DGRAM, 0);

        if (fd != -1) {
		ifr.ifr_addr.sa_family = AF_INET;
		strncpy(ifr.ifr_name , input_name_.c_str() , IFNAMSIZ - 1);

		if (ioctl(fd, SIOCGIFFLAGS, &ifr) == 0) {
			if (ifr.ifr_flags & IFF_RUNNING){
				status = DeviceStatus::RUNNING;
			} else {
				status = DeviceStatus::DOWN;
			}
		}
	}
	::close(fd);

	return status;
}

#endif

} // namespace aiengine
